﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using System.Timers;
using System.Diagnostics;

namespace JGTM.WPF.ActiveDataObject
{
    public struct ActivePropertyRegistration
    {
        public Type HostType;

        public PropertyDescriptor Property;

        //public string PropertyName;

        //public Func<object> ValueReader;

        //public EventInfo TriggerEvent;
    }

    public static class ActiveDataObjectService
    {
        internal static List<Type> RegisteredTypes = new List<Type>();

        internal static List<ActivePropertyRegistration> Registrations = new List<ActivePropertyRegistration>();

        public static void Register(ActivePropertyRegistration registration)
        {
            Registrations.Add(registration);

            if (RegisteredTypes.Contains(registration.HostType))
            {
                TypeDescriptor.Refresh(registration.HostType);
            }
            else
            {
                RegisteredTypes.Add(registration.HostType);

                TypeDescriptor.AddProvider(new ActiveDataObjectTypeDescriptionProvider(), registration.HostType);
            };
        }

        public static void Unregister(Type type)
        {
            throw new NotImplementedException();
        }
    }

    public class ActiveDataObjectTypeDescriptionProvider : TypeDescriptionProvider
    {
        public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
        {
            return new ActiveDataObjectTypeDescriptor(objectType);
        }

        class ActiveDataObjectTypeDescriptor : CustomTypeDescriptor
        {
            private Type _targetType;

            public ActiveDataObjectTypeDescriptor(Type targetType)
            {
                _targetType = targetType;
            }

            public override PropertyDescriptorCollection GetProperties()
            {
                List<PropertyDescriptor> propertieList = new List<PropertyDescriptor>();

                foreach (var registration in ActiveDataObjectService.Registrations)
                {
                    if (registration.HostType == _targetType)
                        propertieList.Add(registration.Property);
                }

                return new PropertyDescriptorCollection(propertieList.ToArray(), true);
            }
        }
    }

    public abstract class ActivePropertyDescriptorBase : PropertyDescriptor
    {
        public ActivePropertyDescriptorBase(PropertyDescriptor descriptor)
            : base(descriptor) { }

        public ActivePropertyDescriptorBase(PropertyDescriptor descriptor, Attribute[] attrs)
            : base(descriptor, attrs) { }

        public ActivePropertyDescriptorBase(string propertyName, Attribute[] attrs)
            : base(propertyName, attrs) { }

        #region ActiveObject Facilities

        private class TriggerCallback
        {
            private readonly object _source;
            private readonly EventHandler _onValueChanged;

            public TriggerCallback(object source, EventHandler onValueChanged)
            {
                _source = source;
                _onValueChanged = onValueChanged;
            }

            /// <summary>
            /// Get called when trigger fires event.
            /// </summary>
            /// <typeparam name="T">Trigger event's EventArg's Type</typeparam>
            /// <param name="sender">Trigger instance.</param>
            /// <param name="arg">Trigger's event's arguments.</param>
            public void GenericCallback<T>(object sender, T arg) where T : EventArgs
            {
                _onValueChanged(_source, arg);
            }

            /// <summary>
            /// Get called when trigger fires event.
            /// </summary>
            /// <param name="sender">Trigger instance (e.g Timer, or instance itself)</param>
            /// <param name="arg">Trigger's event's arguments.</param>
            public void Callback(object sender, EventArgs arg)
            {
                _onValueChanged(_source, arg);
            }
        }

        // when trigger fires event, callback OnValueChanged with affected instance
        protected void NotifyInstanceWhenTriggerFiresEvent(object instance, object trigger, string eventName)
        {
            NotifyInstanceWhenTriggerFiresEvent(instance, trigger, trigger.GetType().GetEvent(eventName));
        }

        // when instance fires the event
        protected void NotifyInstanceWhenTriggerFiresEvent(object instanceAsTrigger, string eventName)
        {
            NotifyInstanceWhenTriggerFiresEvent(instanceAsTrigger, instanceAsTrigger, instanceAsTrigger.GetType().GetEvent(eventName));
        }

        // when trigger fires event, callback OnValueChanged with affected instance
        protected void NotifyInstanceWhenTriggerFiresEvent(object instance, object trigger, EventInfo triggerEvent)
        {
            Type triggerEventHandlerType = triggerEvent.EventHandlerType;

            MethodInfo callbackMethod;

            if (triggerEventHandlerType.ContainsGenericParameters)
            {
                Type triggerEventHandlerTypeGenericArgType = triggerEventHandlerType.GetGenericArguments()[0];
                callbackMethod = typeof(TriggerCallback).GetMethod("GenericCallback");
                callbackMethod = callbackMethod.MakeGenericMethod(triggerEventHandlerTypeGenericArgType);
            }
            else
            {
                callbackMethod = typeof(TriggerCallback).GetMethod("Callback");
            }

            var callbackWrapper = new TriggerCallback(instance, OnValueChanged);
            var callbackDelegate = Delegate.CreateDelegate(triggerEventHandlerType, callbackWrapper, callbackMethod);

            triggerEvent.AddEventHandler(trigger, callbackDelegate); // ==> trigger.Event += callbackDelegate;
        }

        #endregion

        #region Unused overridings

        public override Type ComponentType
        {
            get { throw new NotImplementedException(); }
        }

        public override bool IsReadOnly
        {
            // TODO: implement logic for readonly and read/write scenarios
            get { throw new NotImplementedException(); }
        }

        public override void SetValue(object component, object value)
        {
            throw new NotImplementedException();
        }

        public override bool CanResetValue(object component)
        {
            throw new NotImplementedException();
        }

        public override void ResetValue(object component)
        {
            throw new NotImplementedException();
        }

        public override bool ShouldSerializeValue(object component)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

    public abstract class ExistingPropertyDescriptorBase : ActivePropertyDescriptorBase
    {
        protected PropertyInfo _sourcePropertyInfo;

        public ExistingPropertyDescriptorBase(PropertyInfo sourcePropertyInfo)
            : base(sourcePropertyInfo.Name, null)
        {
            if (sourcePropertyInfo == null)
                throw new ArgumentNullException("sourcePropertyInfo");

            _sourcePropertyInfo = sourcePropertyInfo;
        }

        public override Type PropertyType
        {
            get { return _sourcePropertyInfo.PropertyType; }
        }

        public override object GetValue(object component)
        {
            return _sourcePropertyInfo.GetValue(component, null);
        }

        public override void AddValueChanged(object component, EventHandler handler)
        {
            HookTrigger(component);

            base.AddValueChanged(component, handler);
        }

        protected abstract void HookTrigger(object component);
    }

    public class TriggerEventPropertyDescriptor : ExistingPropertyDescriptorBase
    {
        private EventInfo _triggerEvent;

        public TriggerEventPropertyDescriptor(PropertyInfo sourceProperty, EventInfo triggerEvent)
            : base(sourceProperty)
        {
            if (triggerEvent == null)
                throw new ArgumentNullException("triggerEvent");

            _triggerEvent = triggerEvent;
        }

        public override object GetValue(object component)
        {
            return _sourcePropertyInfo.GetValue(component, null);
        }

        protected override void HookTrigger(object component)
        {
            NotifyInstanceWhenTriggerFiresEvent(component, _triggerEvent.Name);
        }
    }

    public class TriggerEventArgsPropertyDescriptor<PropT> : ActivePropertyDescriptorBase
    {
        private EventInfo _triggerEvent;

        public TriggerEventArgsPropertyDescriptor(string propertyName, EventInfo triggerEvent)
            : base(propertyName, null)
        {
            _triggerEvent = triggerEvent;
        }

        private EventArgs _latestEventArgs = null;

        protected override void OnValueChanged(object component, EventArgs e)
        {
            _latestEventArgs = e;
            base.OnValueChanged(component, e);
        }

        public override void AddValueChanged(object component, EventHandler handler)
        {
            NotifyInstanceWhenTriggerFiresEvent(component, component, _triggerEvent);
            base.AddValueChanged(component, handler);
        }

        public override object GetValue(object component)
        {
            return _latestEventArgs;
        }

        public override Type PropertyType
        {
            get { return typeof(PropT); }
        }
    }

    public class IntervalActivePropertyDescriptor : TriggerEventPropertyDescriptor
    {
        private TimeSpan _triggerInterval;
        private static EventInfo TriggerEvent = typeof(Timer).GetEvent("Elapsed");

        public IntervalActivePropertyDescriptor(PropertyInfo existingProperty, TimeSpan triggerInterval)
            : base(existingProperty, TriggerEvent)
        {
            _triggerInterval = triggerInterval;
        }

        private static Dictionary<object, Timer> triggerMap = new Dictionary<object, Timer>();

        protected override void HookTrigger(object component)
        {
            Timer trigger = null;

            if (triggerMap.ContainsKey(component))
            {
                trigger = triggerMap[component];
                trigger.Interval = _triggerInterval.TotalMilliseconds;
            };

            if (trigger == null)
            {
                trigger = new Timer()
                {
                    Interval = _triggerInterval.TotalMilliseconds,
                    Enabled = true
                };

                triggerMap[component] = trigger;
            }

            NotifyInstanceWhenTriggerFiresEvent(component, trigger, TriggerEvent);
        }
    }
}